package com.ltx.bugseeker.controller;

import cn.dev33.satoken.stp.StpUtil;
import com.ltx.bugseeker.common.Result;
import com.ltx.bugseeker.common.ResultCode;
import com.ltx.bugseeker.dto.SendSmsDTO;
import com.ltx.bugseeker.dto.SmsLoginDTO;
import com.ltx.bugseeker.dto.SetPasswordDTO;
import com.ltx.bugseeker.entry.dto.LoginDTO;
import com.ltx.bugseeker.entry.vo.LoginVO;
import com.ltx.bugseeker.vo.SmsLoginVO;
import com.ltx.bugseeker.service.ISmsService;
import com.ltx.bugseeker.exception.BusinessException;
import com.ltx.bugseeker.service.IUserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.Cookie;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;


/**
 * 登录控制器
 * 处理用户登录、登出等认证相关操作
 * 
 * @author ltx
 * @since 2025-01-27 14:43:42
 */
@Slf4j
@RestController
@RequestMapping("/api/auth")
@RequiredArgsConstructor
@Validated
@Tag(name = "认证管理", description = "用户登录、登出等认证相关接口")
public class LoginController {
    
    /**
     * 用户服务接口
     */
    private final IUserService userService;
    
    /**
     * 短信服务接口
     */
    private final ISmsService smsService;
    
    /**
     * 用户登录
     * 
     * @param loginDTO 登录请求参数，包含用户名、密码等信息
     * @param request HTTP请求对象，用于获取客户端信息
     * @param response HTTP响应对象，用于设置Cookie
     * @return 登录结果，包含用户信息（不包含token）
     * @throws BusinessException 当登录失败时抛出业务异常
     */
    @PostMapping("/login")
    @Operation(summary = "用户登录", description = "用户通过用户名和密码进行登录认证")
    public Result<LoginVO> login(
            @Parameter(description = "登录请求参数", required = true)
            @RequestBody LoginDTO loginDTO,
            HttpServletRequest request,
            HttpServletResponse response) {
        
        log.info("用户登录请求: username={}, rememberMe={}", 
                loginDTO.getUsername(), loginDTO.getRememberMe());
        
        try {
            // 调用Service层处理登录逻辑
            LoginVO loginVO = userService.login(loginDTO, request);
            
            // 获取当前生成的token
            String accessToken = StpUtil.getTokenValue();
            String refreshToken = accessToken + "_refresh"; // 简化的刷新令牌生成
            
            // 将token设置到Cookie中
            setTokenToCookie(response, accessToken, refreshToken, loginDTO.getRememberMe());
            
            // 返回用户信息（不包含token）
            LoginVO responseVO = LoginVO.builder()
                    .userId(loginVO.getUserId())
                    .username(loginVO.getUsername())
                    .realName(loginVO.getRealName())
                    .email(loginVO.getEmail())
                    .avatar(loginVO.getAvatar())
                    .role(loginVO.getRole())
                    .roleName(loginVO.getRoleName())
                    .lastLoginTime(loginVO.getLastLoginTime())
                    .permissions(loginVO.getPermissions())
                    .build();
            
            log.info("用户登录成功: userId={}, username={}", loginVO.getUserId(), loginVO.getUsername());
            
            return Result.success("登录成功", responseVO);
            
        } catch (BusinessException e) {
            log.warn("用户登录失败: username={}, reason={}", loginDTO.getUsername(), e.getMessage());
            throw e;
        } catch (Exception e) {
            log.error("用户登录异常: username={}", loginDTO.getUsername(), e);
            throw new BusinessException(ResultCode.SYSTEM_ERROR, "登录失败，请稍后重试");
        }
    }
    
    /**
     * 用户登出
     * 
     * @return 登出结果
     * @throws BusinessException 当登出失败时抛出业务异常
     */
    @PostMapping("/logout")
    @Operation(summary = "用户登出", description = "用户退出登录，清除认证信息")
    public Result<Void> logout() {
        
        try {
            // 获取当前登录用户ID
            Long userId = null;
            if (StpUtil.isLogin()) {
                userId = StpUtil.getLoginIdAsLong();
            }
            
            log.info("用户登出请求: userId={}", userId);
            
            // 调用Service层处理登出逻辑
            userService.logout();
            
            log.info("用户登出成功: userId={}", userId);
            
            return Result.success("登出成功");
            
        } catch (Exception e) {
            log.error("用户登出异常", e);
            throw new BusinessException(ResultCode.SYSTEM_ERROR, "登出失败，请稍后重试");
        }
    }
    
    /**
     * 获取当前登录用户信息
     * 
     * @return 当前登录用户信息
     * @throws BusinessException 当用户未登录时抛出业务异常
     */
    @GetMapping("/current")
    @Operation(summary = "获取当前用户信息", description = "获取当前登录用户的详细信息")
    public Result<LoginVO> getCurrentUser() {
        
        try {
            // 检查登录状态
            if (!StpUtil.isLogin()) {
                throw new BusinessException(ResultCode.UNAUTHORIZED, "用户未登录");
            }
            
            Long userId = StpUtil.getLoginIdAsLong();
            log.info("获取当前用户信息: userId={}", userId);
            
            // 调用Service层获取用户信息
            LoginVO loginVO = userService.getCurrentUserInfo(userId);
            
            return Result.success("获取用户信息成功",loginVO);
            
        } catch (BusinessException e) {
            throw e;
        } catch (Exception e) {
            log.error("获取当前用户信息异常", e);
            throw new BusinessException(ResultCode.SYSTEM_ERROR, "获取用户信息失败");
        }
    }
    
    /**
     * 刷新访问令牌
     * 
     * @param refreshToken 刷新令牌
     * @param response HTTP响应对象，用于设置新的Cookie
     * @return 新的访问令牌信息
     * @throws BusinessException 当刷新令牌无效时抛出业务异常
     */
    @PostMapping("/refresh")
    @Operation(summary = "刷新访问令牌", description = "使用刷新令牌获取新的访问令牌")
    public Result<LoginVO> refreshToken(
            @Parameter(description = "刷新令牌", required = true)
            @RequestParam String refreshToken,
            HttpServletResponse response) {
        
        log.info("刷新访问令牌请求: refreshToken={}", refreshToken);
        
        try {
            // 调用Service层处理令牌刷新
            LoginVO loginVO = userService.refreshToken(refreshToken);
            
            // 获取新的访问令牌
            String newAccessToken = StpUtil.getTokenValue();
            
            // 将新的token设置到Cookie中
            setTokenToCookie(response, newAccessToken, refreshToken + "_refresh", true);
            
            log.info("刷新访问令牌成功: userId={}", loginVO.getUserId());
            
            return Result.success("令牌刷新成功", loginVO);
            
        } catch (BusinessException e) {
            log.warn("刷新访问令牌失败: refreshToken={}, reason={}", refreshToken, e.getMessage());
            throw e;
        } catch (Exception e) {
            log.error("刷新访问令牌异常: refreshToken={}", refreshToken, e);
            throw new BusinessException(ResultCode.SYSTEM_ERROR, "令牌刷新失败");
        }
    }
    
    /**
     * 检查用户名是否可用
     * 
     * @param username 用户名
     * @return 用户名可用性检查结果
     * @throws BusinessException 当参数无效时抛出业务异常
     */
    @GetMapping("/check-username")
    @Operation(summary = "检查用户名可用性", description = "检查指定用户名是否已被使用")
    public Result<Boolean> checkUsername(
            @Parameter(description = "用户名", required = true)
            @RequestParam String username) {
        
        log.info("检查用户名可用性: username={}", username);
        
        try {
            // 调用Service层检查用户名可用性
            boolean available = userService.checkUsernameAvailability(username);
            
            return Result.success(available ? "用户名可用" : "用户名已被使用",available);
            
        } catch (BusinessException e) {
            throw e;
        } catch (Exception e) {
            log.error("检查用户名可用性异常: username={}", username, e);
            throw new BusinessException(ResultCode.SYSTEM_ERROR, "检查用户名失败");
        }
    }
    
    /**
     * 发送短信验证码
     * 
     * @param sendSmsDTO 发送短信请求参数
     * @return 发送结果
     * @throws BusinessException 当发送失败时抛出业务异常
     */
    @PostMapping("/send-sms")
    @Operation(summary = "发送短信验证码", description = "向指定手机号发送短信验证码")
    public Result<Void> sendSms(
            @Parameter(description = "发送短信请求参数", required = true)
            @RequestBody @Validated SendSmsDTO sendSmsDTO) {
        
        log.info("发送短信验证码请求: phone={}, type={}", sendSmsDTO.getPhone(), sendSmsDTO.getType());
        
        try {
            // 调用Service层发送短信验证码
            smsService.sendSmsCode(sendSmsDTO);
            
            log.info("短信验证码发送成功: phone={}", sendSmsDTO.getPhone());
            
            return Result.success("验证码发送成功");
            
        } catch (BusinessException e) {
            log.warn("短信验证码发送失败: phone={}, reason={}", sendSmsDTO.getPhone(), e.getMessage());
            throw e;
        } catch (Exception e) {
            log.error("短信验证码发送异常: phone={}", sendSmsDTO.getPhone(), e);
            throw new BusinessException(ResultCode.SYSTEM_ERROR, "验证码发送失败");
        }
    }
    
    /**
     * 短信验证码登录
     * 
     * @param smsLoginDTO 短信登录请求参数
     * @param request HTTP请求对象，用于获取客户端信息
     * @param response HTTP响应对象，用于设置Cookie
     * @return 登录结果，包含用户信息（不包含token）
     * @throws BusinessException 当登录失败时抛出业务异常
     */
    @PostMapping("/sms-login")
    @Operation(summary = "短信验证码登录", description = "用户通过手机号和短信验证码进行登录认证")
    public Result<SmsLoginVO> smsLogin(
            @Parameter(description = "短信登录请求参数", required = true)
            @RequestBody @Validated SmsLoginDTO smsLoginDTO,
            HttpServletRequest request,
            HttpServletResponse response) {
        
        log.info("短信验证码登录请求: phone={}", smsLoginDTO.getPhone());
        
        try {
            // 调用Service层处理短信登录逻辑
            SmsLoginVO smsLoginVO = userService.smsLogin(smsLoginDTO, request);

            // 从响应对象中移除token信息，只返回用户信息
            SmsLoginVO responseVO = new SmsLoginVO();
            responseVO.setUserInfo(smsLoginVO.getUserInfo());
            responseVO.setNeedSetPassword(smsLoginVO.getNeedSetPassword());
            responseVO.setExpiresIn(smsLoginVO.getExpiresIn());
            
            log.info("短信验证码登录成功: phone={}, userId={}, needSetPassword={}", 
                    smsLoginDTO.getPhone(), smsLoginVO.getUserInfo().getId(), smsLoginVO.getNeedSetPassword());
            
            return Result.success("登录成功", responseVO);
            
        } catch (BusinessException e) {
            log.warn("短信验证码登录失败: phone={}, reason={}", smsLoginDTO.getPhone(), e.getMessage());
            throw e;
        } catch (Exception e) {
            log.error("短信验证码登录异常: phone={}", smsLoginDTO.getPhone(), e);
            throw new BusinessException(ResultCode.SYSTEM_ERROR, "登录失败，请稍后重试");
        }
    }
    
    /**
     * 设置密码
     * 
     * @param setPasswordDTO 设置密码请求参数
     * @return 设置结果
     * @throws BusinessException 当设置失败时抛出业务异常
     */
    @PostMapping("/set-password")
    @Operation(summary = "设置密码", description = "用户首次登录后设置密码")
    public Result<Void> setPassword(
            @Parameter(description = "设置密码请求参数", required = true)
            @RequestBody @Validated SetPasswordDTO setPasswordDTO) {
        
        try {
            // 检查登录状态
            if (!StpUtil.isLogin()) {
                throw new BusinessException(ResultCode.UNAUTHORIZED, "用户未登录");
            }
            
            Long userId = StpUtil.getLoginIdAsLong();
            log.info("设置密码请求: userId={}", userId);
            
            // 调用Service层设置密码
            boolean success = userService.setPassword(userId, setPasswordDTO);
            
            if (success) {
                log.info("密码设置成功: userId={}", userId);
                return Result.success("密码设置成功");
            } else {
                throw new BusinessException(ResultCode.SYSTEM_ERROR, "密码设置失败");
            }
            
        } catch (BusinessException e) {
            throw e;
        } catch (Exception e) {
            log.error("设置密码异常", e);
            throw new BusinessException(ResultCode.SYSTEM_ERROR, "密码设置失败");
        }
    }
    
    /**
     * 检查手机号是否已存在
     * 
     * @param phone 手机号
     * @return 手机号存在性检查结果
     * @throws BusinessException 当参数无效时抛出业务异常
     */
    @GetMapping("/check-phone")
    @Operation(summary = "检查手机号是否已存在", description = "检查指定手机号是否已被注册")
    public Result<Boolean> checkPhone(
            @Parameter(description = "手机号", required = true)
            @RequestParam String phone) {
        
        log.info("检查手机号是否已存在: phone={}", phone);
        
        try {
            // 调用Service层检查手机号是否已存在
            boolean exists = userService.checkPhoneExists(phone);
            
            return Result.success(exists ? "手机号已存在" : "手机号可用", exists);
            
        } catch (BusinessException e) {
            throw e;
        } catch (Exception e) {
            log.error("检查手机号异常: phone={}", phone, e);
            throw new BusinessException(ResultCode.SYSTEM_ERROR, "检查手机号失败");
        }
    }
    
    /**
     * 将访问令牌和刷新令牌设置到Cookie中
     * 
     * @param response HTTP响应对象
     * @param accessToken 访问令牌
     * @param refreshToken 刷新令牌
     * @param rememberMe 是否记住登录状态
     */
    private void setTokenToCookie(HttpServletResponse response, String accessToken, String refreshToken, Boolean rememberMe) {
        try {
            // 设置访问令牌Cookie
            Cookie accessTokenCookie = new Cookie("ACCESS_TOKEN", accessToken);
            accessTokenCookie.setHttpOnly(true); // 防止XSS攻击
            accessTokenCookie.setPath("/"); // 设置路径为根路径
            accessTokenCookie.setSecure(false); // 开发环境设置为false，生产环境应设置为true
            
            // 根据rememberMe设置不同的过期时间
            if (rememberMe != null && rememberMe) {
                // 记住登录状态：7天
                accessTokenCookie.setMaxAge(7 * 24 * 60 * 60);
            } else {
                // 不记住登录状态：会话结束时过期
                accessTokenCookie.setMaxAge(-1);
            }
            
            response.addCookie(accessTokenCookie);
            
            // 设置刷新令牌Cookie
            Cookie refreshTokenCookie = new Cookie("REFRESH_TOKEN", refreshToken);
            refreshTokenCookie.setHttpOnly(true); // 防止XSS攻击
            refreshTokenCookie.setPath("/"); // 设置路径为根路径
            refreshTokenCookie.setSecure(false); // 开发环境设置为false，生产环境应设置为true
            
            // 刷新令牌通常有更长的过期时间
            if (rememberMe != null && rememberMe) {
                // 记住登录状态：30天
                refreshTokenCookie.setMaxAge(30 * 24 * 60 * 60);
            } else {
                // 不记住登录状态：7天
                refreshTokenCookie.setMaxAge(7 * 24 * 60 * 60);
            }
            
            response.addCookie(refreshTokenCookie);
            
            log.info("Token已成功设置到Cookie中: accessToken={}, refreshToken={}, rememberMe={}", 
                    accessToken != null ? "***" : null, 
                    refreshToken != null ? "***" : null, 
                    rememberMe);
                    
        } catch (Exception e) {
            log.error("设置Token到Cookie失败", e);
            throw new BusinessException(ResultCode.SYSTEM_ERROR, "设置登录状态失败");
        }
    }
}